/** \file    gdiplus.cpp
  * \brief   Image IO operations using GDI+
  * \date    2003
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/imglib/src/gdiplus.cpp,v 1.9 2007/07/12 20:04:53 cscom1r4 Exp $
  */

#include "imglib/gdiplus.h"
#include "imglib/winimage.h"
#include "imglib/io.h"
#include <fstream>
#include <assert.h>

#include "windows.h"

/*
 * These definitions are required to allow polymorphic structure members (i.e. those
 * that are referred to both as DWORDs and as pointers) to resolve into a type
 * of correct size to hold the largest of those two types (i.e. pointer) on 64 bit
 * systems. For 32 bit environments, ULONG_PTR resolves to a DWORD.
 */
#ifndef MAXULONG_PTR
#define ULONG_PTR    DWORD
#endif //MAXULONG_PTR

#pragma warning(push)
#ifdef __INTEL_COMPILER
	#pragma warning(disable : 424)
	#pragma warning(disable : 271)
	#pragma warning(disable : 1125)
#endif
#include "GdiPlus.h"
#pragma warning(pop)

#pragma comment(lib, "GdiPlus.lib")

namespace img {

class CppStreamBase : public IStream
{
public:
    virtual HRESULT STDMETHODCALLTYPE QueryInterface( 
        /* [in] */ REFIID riid,
        /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
	{
		if (riid == IID_IUnknown || riid == IID_ISequentialStream || riid == IID_IStream) {
			*ppvObject = this;
			return S_OK;
		}
		else
			return E_NOINTERFACE;
	}
    
    virtual ULONG STDMETHODCALLTYPE AddRef()
		{ return 1; }
    
    virtual ULONG STDMETHODCALLTYPE Release()
		{ return 1; }

    virtual HRESULT STDMETHODCALLTYPE SetSize( 
        /* [in] */ ULARGE_INTEGER libNewSize)
		{ return E_NOTIMPL; }
    
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE CopyTo( 
        /* [unique][in] */ IStream __RPC_FAR *pstm,
        /* [in] */ ULARGE_INTEGER cb,
        /* [out] */ ULARGE_INTEGER __RPC_FAR *pcbRead,
        /* [out] */ ULARGE_INTEGER __RPC_FAR *pcbWritten)
		{ return E_NOTIMPL; }
	
    
    virtual HRESULT STDMETHODCALLTYPE Commit( 
        /* [in] */ DWORD grfCommitFlags)
		{ return E_NOTIMPL; }
    
    virtual HRESULT STDMETHODCALLTYPE Revert()
		{ return E_NOTIMPL; }
    
    virtual HRESULT STDMETHODCALLTYPE LockRegion( 
        /* [in] */ ULARGE_INTEGER libOffset,
        /* [in] */ ULARGE_INTEGER cb,
        /* [in] */ DWORD dwLockType)
		{ return E_NOTIMPL; }
    
    virtual HRESULT STDMETHODCALLTYPE UnlockRegion( 
        /* [in] */ ULARGE_INTEGER libOffset,
        /* [in] */ ULARGE_INTEGER cb,
        /* [in] */ DWORD dwLockType)
		{ return E_NOTIMPL; }
    
    virtual HRESULT STDMETHODCALLTYPE Stat( 
        /* [out] */ STATSTG __RPC_FAR *pstatstg,
        /* [in] */ DWORD grfStatFlag)
	{
		pstatstg->pwcsName = 0;
		pstatstg->type = STGTY_STREAM;
		pstatstg->cbSize.QuadPart = 0;
		pstatstg->mtime.dwLowDateTime = pstatstg->mtime.dwHighDateTime = 0;
		pstatstg->ctime.dwLowDateTime = pstatstg->ctime.dwHighDateTime = 0;
		pstatstg->atime.dwLowDateTime = pstatstg->atime.dwHighDateTime = 0;
		pstatstg->grfMode = STGM_WRITE;
		pstatstg->grfLocksSupported = 0;
		pstatstg->grfStateBits = 0;
		pstatstg->clsid = CLSID_NULL;
		return S_OK;
	}
    
    virtual HRESULT STDMETHODCALLTYPE Clone( 
        /* [out] */ IStream __RPC_FAR *__RPC_FAR *ppstm)
		{ return E_NOTIMPL; }
};

class CppOutStream : public CppStreamBase
{
	std::ostream & _ostream;
public:
	CppOutStream(std::ostream & s) : _ostream(s) {}

    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Read( 
        /* [length_is][size_is][out] */ void __RPC_FAR *pv,
        /* [in] */ ULONG cb,
        /* [out] */ ULONG __RPC_FAR *pcbRead)
		{ return S_FALSE; }
    
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Write( 
        /* [size_is][in] */ const void __RPC_FAR *pv,
        /* [in] */ ULONG cb,
        /* [out] */ ULONG __RPC_FAR *pcbWritten)
	{
		_ostream.write((char*)pv, cb);
		if (pcbWritten)
			*pcbWritten = cb;
		return S_OK;
	}

    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Seek( 
        /* [in] */ LARGE_INTEGER dlibMove,
        /* [in] */ DWORD dwOrigin,
        /* [out] */ ULARGE_INTEGER __RPC_FAR *plibNewPosition)
	{
		std::ios_base::seek_dir d;
		switch(dwOrigin) {
		case STREAM_SEEK_SET:
			d = std::ios_base::beg;
			break;
		case STREAM_SEEK_CUR:
			d = std::ios_base::cur;
			break;
		case STREAM_SEEK_END:
			d = std::ios_base::end;
			break;
		default:
			assert(false);
		}

		//_ostream.seekp(std::ios::off_type(dlibMove.QuadPart), d);
		_ostream.seekp(std::ostream::off_type(dlibMove.QuadPart), d);
		if (plibNewPosition)
			plibNewPosition->QuadPart = _ostream.tellp();
		return S_OK;
	}
};

class CppInStream : public CppStreamBase
{
	std::istream & _istream;
public:
	CppInStream(std::istream & s) : _istream(s) {}

    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Read( 
        /* [length_is][size_is][out] */ void __RPC_FAR *pv,
        /* [in] */ ULONG cb,
        /* [out] */ ULONG __RPC_FAR *pcbRead)
	{
		_istream.read((char*)pv, cb);
		if (pcbRead)
			*pcbRead = _istream.gcount();
		return S_OK;
	}
    
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Write( 
        /* [size_is][in] */ const void __RPC_FAR *pv,
        /* [in] */ ULONG cb,
        /* [out] */ ULONG __RPC_FAR *pcbWritten)
		{ return S_FALSE; }

    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Seek( 
        /* [in] */ LARGE_INTEGER dlibMove,
        /* [in] */ DWORD dwOrigin,
        /* [out] */ ULARGE_INTEGER __RPC_FAR *plibNewPosition)
	{
		std::ios_base::seek_dir d;
		switch(dwOrigin) {
		case STREAM_SEEK_SET:
			d = std::ios_base::beg;
			break;
		case STREAM_SEEK_CUR:
			d = std::ios_base::cur;
			break;
		case STREAM_SEEK_END:
			d = std::ios_base::end;
			break;
		default:
			assert(false);
		}

		_istream.seekg(std::ios::off_type(dlibMove.QuadPart), d);
		if (plibNewPosition)
			plibNewPosition->QuadPart = _istream.tellg();
		return S_OK;
	}

    virtual HRESULT STDMETHODCALLTYPE Stat( 
        /* [out] */ STATSTG __RPC_FAR *pstatstg,
        /* [in] */ DWORD grfStatFlag)
	{
		CppStreamBase::Stat(pstatstg, grfStatFlag);
		std::ios::pos_type pos = _istream.tellg();
		_istream.seekg(0, std::ios_base::end);
		pstatstg->cbSize.QuadPart = _istream.tellg();
		_istream.seekg(pos, std::ios_base::beg);
		return S_OK;
	}
};

using namespace Gdiplus;

static int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
   UINT  num = 0;          // number of image encoders
   UINT  size = 0;         // size of the image encoder array in bytes

   ImageCodecInfo* pImageCodecInfo = NULL;

   GetImageEncodersSize(&num, &size);
   if(size == 0)
      return -1;  // Failure

   pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
   if(pImageCodecInfo == NULL)
      return -1;  // Failure

   GetImageEncoders(num, size, pImageCodecInfo);

   for(UINT j = 0; j < num; ++j)
   {
      if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
      {
         *pClsid = pImageCodecInfo[j].Clsid;
         free(pImageCodecInfo);
         return j;  // Success
      }    
   }

   free(pImageCodecInfo);
   return -1;  // Failure
}

static const WCHAR * encoders_names[] = 
{
	L"image/bmp",
	L"image/jpeg",
	L"image/gif",
	L"image/tiff",
	L"image/png"
};

static int GetEncoderClsid(format f, CLSID* pClsid)
{
	assert(f != any_format);
	return GetEncoderClsid(encoders_names[f], pClsid);
}

class CInitGDIPlus {
	bool init_ok;
public:
	CInitGDIPlus() {
		ULONG_PTR token;
		Gdiplus::GdiplusStartupInput input;
		Gdiplus::GdiplusStartupOutput output;
		init_ok = 
			Ok == GdiplusStartup(&token, &input, &output);
	}
	void Test() {
		if (!init_ok)
			throw std::runtime_error("GDI+ init failed");
	}
} InitGDIPlus;

/////////////
// Encoder //
/////////////

template <class R>
PixelFormat format_constant(bool alpha);

template <>
PixelFormat format_constant<bitmap>(bool alpha)
	{ return PixelFormat1bppIndexed; }

template <>
PixelFormat format_constant<mono_byte_image>(bool alpha)
	{ return PixelFormat8bppIndexed; }

template <>
PixelFormat format_constant<small_byte_image>(bool alpha)
	{ return PixelFormat24bppRGB; }

template <>
PixelFormat format_constant<byte_image>(bool alpha)
	{ return alpha ? PixelFormat32bppARGB : PixelFormat32bppRGB; }

template <class R>
static std::auto_ptr<Bitmap> generic_create(R const & r, bool alpha)
{
	InitGDIPlus.Test();

	//best way of creation of Bitmap
	return std::auto_ptr<Bitmap>(
		new Bitmap(r.width(), r.height(),
			(r.order() == top_down) ? (int)r.scansize() : -(int)r.scansize(),
			format_constant<R>(alpha), (BYTE*)r.scan(0)));
}

template <>
std::auto_ptr<Bitmap> create(img::bitmap const & r, bool alpha)
	{ return generic_create(r, alpha); }

struct ColorPalette256
{
    UINT Flags;
    UINT Count;
	Gdiplus::ARGB Entries[256];
};

template <>
std::auto_ptr<Bitmap> create(mono_byte_image const & r, bool alpha) 
{
	std::auto_ptr<Bitmap> a(generic_create(r, alpha));

	ColorPalette256 cp;
	cp.Flags = PaletteFlagsGrayScale;
	cp.Count = 256;

	for (int i = 0; i < 256; ++i)
		cp.Entries[i] = RGB(i, i, i);

	if (Ok != a->SetPalette(reinterpret_cast<ColorPalette*>(&cp)))
		throw io_error("Gdiplus::Bitmap::SetPalette failure", "");

	return a;
}

template <>
std::auto_ptr<Bitmap> create(small_byte_image const & r, bool alpha)
	{ return generic_create(r, alpha); }

template <>
std::auto_ptr<Bitmap> create(byte_image const & r, bool alpha)
	{ return generic_create(r, alpha); }

/* alternative implementation
template <class R>
std::ostream & operator << (std::ostream & s, gdiplus_encoder<R> & c)
{
	const R * aimg = &c.img;
	R copy;
	if ((c.img.scansize() % 4) != 0) { // data is not aligned properly
		copy.order(c.img.order());
		copy.resize(c.img.width(), c.img.height(), 4);
		copy = c.img;
		aimg = &copy;
	}

	InitGDIPlus.Test();

	CLSID formatClsid;
	GetEncoderClsid(c.fmt, &formatClsid);

	bitmap_info<R> info;
	Bitmap bm(ComposeBitmapInfo(info, *aimg), const_cast<void*>(aimg->data()));

	CppOutStream cs(s);
	if (Ok != bm.Save(&cs, &formatClsid, 0))
		throw io_error("Gdiplus::Bitmap::Save failure", "");

	return s;
}*/

template <class R>
static std::ostream & generic_write(std::ostream & s, gdiplus_encoder<R> & c)
{
	InitGDIPlus.Test();

	const R * aimg = &c.img;
	R copy;
	if ((c.img.scansize() % 4) != 0) { // data is not aligned properly
		copy.order(c.img.order());
		copy.resize(c.img.width(), c.img.height(), 4);
		copy = c.img;
		aimg = &copy;
	}

	std::auto_ptr<Bitmap> bm = create(*aimg, c.alpha);

	CppOutStream cs(s);

	CLSID formatClsid;
	GetEncoderClsid(c.fmt, &formatClsid);

	if (Ok != bm->Save(&cs, &formatClsid, 0))
		throw io_error("Gdiplus::Bitmap::Save failure", "");

	return s;
}

template <class R>
std::ostream & operator << (std::ostream & s, gdiplus_encoder<R> & c)
	{ return generic_write(s, c); }

template std::ostream & operator << (std::ostream & s, gdiplus_encoder<bitmap> & c);
template std::ostream & operator << (std::ostream & s, gdiplus_encoder<mono_byte_image> & c);
template std::ostream & operator << (std::ostream & s, gdiplus_encoder<small_byte_image> & c);

template <>
std::ostream & operator << (std::ostream & s, gdiplus_encoder<byte_image> & c)
{
	if (c.alpha)
		return generic_write(s, c);

	//unfortunately, direct use of PixelFormat32bppRGB leaves extra alpha channel in written image,
	//although marks it to be ignored

	small_byte_image copy2(c.img.dim(), 4);
	copy2 = c.img;
	return s << gdiplus(copy2, c.fmt);
}

/////////////////////
// Encoder/Decoder //
/////////////////////

template <class R>
static std::istream & generic_read(std::istream & s, gdiplus_coder<R> & c)
{
	assert(c.fmt == any_format);
	InitGDIPlus.Test();

	CppInStream cs(s);
	Bitmap bm(&cs, false);
	unsigned h = bm.GetHeight(), w = bm.GetWidth();
	if (h == 0 || w == 0)
		throw io_error("Gdiplus::Bitmap reading failure", "");

	//best way of creation of Bitmap from raster
	c.img.resize(w, h, 4);
	Rect rect(0, 0, w, h);
	BitmapData data = {w, h, 
		(c.img.order() == top_down) ? c.img.scansize() : -(int)c.img.scansize(), 
		format_constant<R>(true), c.img.scan(0), 0};

	if (Ok != bm.LockBits(&rect, ImageLockModeRead | ImageLockModeUserInputBuf,
						  format_constant<R>(true), &data))
		throw io_error("Gdiplus::Bitmap::LockBits failure", "");
	//do nothing, lock bits already copied data into c.img
	if (Ok != bm.UnlockBits(&data))
		throw io_error("Gdiplus::Bitmap::UnlockBits failure", "");

	if (c.read_alpha)
		*c.read_alpha = IsAlphaPixelFormat(bm.GetPixelFormat()) != FALSE;

	return s;
}

template <class R>
std::istream & operator >> (std::istream & s, gdiplus_coder<R> & c)
	{ return generic_read(s, c); }

template std::istream & operator >> (std::istream & s, gdiplus_coder<bitmap> & c);
template std::istream & operator >> (std::istream & s, gdiplus_coder<small_byte_image> & c);
template std::istream & operator >> (std::istream & s, gdiplus_coder<byte_image> & c);

struct blue_component {
	typedef unsigned char result_type;
	unsigned char operator()(byte_color const & c)
		{ return c.b; }
};

template <>
std::istream & operator >> (std::istream & s, gdiplus_coder<mono_byte_image> & c)
{
	//Gdiplus::Bitmap::LockBits for indexed color image fails, this is workaround
	byte_image im;
	s >> gdiplus(im, c.read_alpha);
	c.img = modify(im, blue_component());
	return s;
}

/*	//alternative way of taking bits from Gdiplus::Bitmap
	HBITMAP hbm;
	bm.GetHBITMAP(RGB(0,0,0), &hbm);

	bitmap_info info;
	c.bmp.resize(bm.GetWidth(), bm.GetHeight(), 4);
	HDC dc = CreateCompatibleDC(0);

	if (!GetDIBits(dc, hbm, 0, bm.GetHeight(), c.bmp.data(), 
			ComposeBitmapInfo(info, c.bmp, RGB(255,255,255), RGB(0,0,0)),
			DIB_RGB_COLORS))
	{
		DeleteDC(dc);
		throw std::runtime_error("GetDIBits failed");
	}
	DeleteDC(dc); */

////////////////////
// gdiplus_reader //
////////////////////

bool gdiplus_reader::ping(const std::string & path, file_info * info) const
{
	try {
		std::ifstream file(path.c_str(), std::ios::binary);
		if (!file)
			throw io_error("could not open image file for reading or read error", path.c_str());
		//No exceptions: GDI+ catches them and behaves wrongly
		//file.exceptions(std::ios::badbit | std::ios::failbit);

		byte_image img;
		bool alpha;
		file >> gdiplus(img, &alpha);
		if (info) {
			info->path = path;
			info->format = "???"; //Bitmap::GetRawFormat
			info->dim = img.dim();
			info->bit_count = alpha ? 32 : 24;
			info->compressed = false; //???
		};
		return true;
	}
	catch (const std::exception &) {
		return false;
	}
}

bool gdiplus_reader::ping(const std::string & path, const std::string & format, file_info * info) const
{
	return ping(path, info);
}

bool gdiplus_reader::fast_ping() const
{
	return false;
}

void gdiplus_reader::read(const std::string & path, byte_image & img) const
{
	try {
		std::ifstream file(path.c_str(), std::ios::binary);
		if (!file)
			throw io_error("could not open image file for reading or read error", path.c_str());
		//No exceptions: GDI+ catches them and behaves wrongly
		//file.exceptions(std::ios::badbit | std::ios::failbit);
		file >> gdiplus(img);
		return;
	}
	catch (io_error & e) {
		e.filename(path);
		throw;
	}
}

void gdiplus_reader::read(const std::string & path, const std::string & format, byte_image & img) const
{
	read(path, img);
}

void gdiplus_reader::formats(ext_vec & f) const
{
	f.clear();
	f.push_back("BMP");
	f.push_back("JPEG");
	f.push_back("JPG");
	f.push_back("GIF");
	f.push_back("TIF");
	f.push_back("TIFF");
	f.push_back("PNG");
}

std::string gdiplus_reader::search_str() const
{
	return "*.BMP;*.JPEG;*.JPG;*.GIF;*.TIF;*.TIFF;*.PNG";
}

////////////////////
// gdiplus_writer //
////////////////////

size_t gdiplus_writer::write(const std::string & path, const std::string & format, const byte_image & img, bool alpha)
{
	try {
		std::ofstream file(path.c_str(), std::ios::binary);
		if (!file)
			throw io_error("could not open image file for writing or write error", path.c_str());
		//No exceptions: GDI+ catches them and behaves wrongly
		//file.exceptions(std::ios::badbit | std::ios::failbit);
		if (_stricmp(format.c_str(), "BMP") == 0) {
			file << gdiplus(img, bmp_format, alpha);
			return file.tellp();
		}
		if (_stricmp(format.c_str(), "JPEG") == 0 || _stricmp(format.c_str(), "JPG") == 0) {
			file << gdiplus(img, jpeg_format, alpha);
			return file.tellp();
		}
		if (_stricmp(format.c_str(), "GIF") == 0) {
			file << gdiplus(img, gif_format, alpha);
			return file.tellp();
		}
		if (_stricmp(format.c_str(), "TIFF") == 0 || _stricmp(format.c_str(), "TIF") == 0) {
			file << gdiplus(img, tiff_format, alpha);
			return file.tellp();
		}
		if (_stricmp(format.c_str(), "PNG") == 0) {
			file << gdiplus(img, png_format, alpha);
			return file.tellp();
		}
	}
	catch (io_error & e) {
		e.filename(path);
		throw;
	}

	throw io_error("file format is not supported", format);
}

void gdiplus_writer::formats(ext_vec & f) const
{
	f.clear();
	f.push_back("BMP");
	f.push_back("JPEG");
	f.push_back("JPG");
	f.push_back("GIF");
	f.push_back("TIF");
	f.push_back("TIFF");
	f.push_back("PNG");
}

std::string gdiplus_writer::search_str() const
{
	return "*.BMP;*.JPEG;*.JPG;*.GIF;*.TIF;*.TIFF;*.PNG";
}

/////////////////////////////
// multireader/multiwriter //
/////////////////////////////

our_gdiplus_multireader::our_gdiplus_multireader()
{
	insert(new our_reader());
	insert(new gdiplus_reader());
}

our_gdiplus_multiwriter::our_gdiplus_multiwriter()
{
	insert(new our_writer());
	insert(new gdiplus_writer());
}

} //namespace img
